package com.googlecode.javarpctp.client;

import com.googlecode.javarpctp.test.common.ThreadCreatedEvent;
import com.googlecode.javarpctp.communication.ConnectionHandler;
import com.googlecode.javarpctp.test.common.ThreadListener;
import com.googlecode.javarpctp.communication.ConnectionHandlerMessageTransportEvent;
import com.googlecode.javarpctp.communication.ConnectionHandlerMessageTransportListener;
import com.googlecode.javarpctp.communication.ConnectionStateChangedEvent;
import com.googlecode.javarpctp.communication.ConnectionStateChangedListener;
import com.googlecode.javarpctp.communication.ConnectionThread;
import com.googlecode.javarpctp.communication.CustomMessageRecievedEvent;
import com.googlecode.javarpctp.communication.CustomMessageRecievedListener;
import com.googlecode.javarpctp.remote.RemoteDispatcher;
import com.googlecode.javarpctp.remote.RemoteService;
import com.googlecode.javarpctp.server.ServerMessage;
import java.net.Socket;
import java.util.EventObject;
import javax.swing.event.EventListenerList;

public class Client {

    private String host;
    private int port;
    private ConnectionThread connectionThread;
    protected EventListenerList listenerList = new EventListenerList();
    private DisconnectionCause disconnectionCause;
    private Exception disconnectionException;
    private RemoteDispatcher remoteDispatcher = new RemoteDispatcher();

    public Client(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void connect() throws ClientException {
        if (this.connectionThread != null && this.connectionThread.isAlive()) {
            throw new ClientException("Already connected");
        } else {
            try {
                this.disconnectionCause = null;
                this.disconnectionException = null;
                Socket connectedSocket = new Socket(this.host, this.port);

                if (connectedSocket.isConnected()) {
                    this.connectionThread = new ConnectionThread(new ConnectionHandler(connectedSocket), "Connection listener");
                    this.fireConnected(new EventObject(this));

                    this.connectionThread.getConnectionHandler().addCustomMessageRecievedListener(new CustomMessageRecievedListener() {

                        @Override
                        public void customMessageRecieved(CustomMessageRecievedEvent evt) {
                            ServerMessage serverMessage = (ServerMessage) evt.getMessage();
                            switch (serverMessage.getServerCode()) {
                                case REQUEST_STOP:
                                    Client.this.connectionThread.getConnectionHandler().stop();
                                    break;
                            }
                        }
                    });

                    this.connectionThread.getConnectionHandler().addConnectionStateChangedListener(new ConnectionStateChangedListener() {

                        @Override
                        public void stateChanged(ConnectionStateChangedEvent evt) {
                            switch (evt.getState()) {
                                case STOPED:
                                    ConnectionHandler source = (ConnectionHandler) evt.getSource();
                                    ClientDisconnectedEvent disconnectedEvent;
                                    switch (source.getStopCause()) {
                                        case REQUESTED:
                                            Client.this.setDisconnectionCause(DisconnectionCause.REQUESTED);
                                            disconnectedEvent = new ClientDisconnectedEvent(Client.this, DisconnectionCause.REQUESTED);
                                            break;
                                        case EXCEPTION:
                                            Client.this.setDisconnectionCause(DisconnectionCause.EXCEPTION);
                                            Client.this.setDisconnectionException(source.getStopException());
                                            disconnectedEvent = new ClientDisconnectedEvent(Client.this, DisconnectionCause.EXCEPTION, source.getStopException());
                                            break;
                                        default:
                                            disconnectedEvent = new ClientDisconnectedEvent(Client.this, DisconnectionCause.UNKNOWN);
                                    }
                                    Client.this.fireDisconnected(disconnectedEvent);
                            }
                        }
                    });

                    this.connectionThread.getConnectionHandler().addThreadListener(new ThreadListener() {

                        @Override
                        public void threadCreated(ThreadCreatedEvent evt) {
                            Client.this.fireThreadCreated(evt);
                        }
                    });

                    this.connectionThread.getConnectionHandler().addConnectionHandlerMessageTransportListener(new ConnectionHandlerMessageTransportListener() {

                        @Override
                        public void messageTransported(ConnectionHandlerMessageTransportEvent evt) {
                            Client.this.fireMessageTransported(evt);
                        }
                    });

                    this.fireThreadCreated(new ThreadCreatedEvent(this, connectionThread));
                    this.connectionThread.start();
                }
            } catch (Exception ex) {
                throw new ClientException("Cannot connect to server", ex);
            }
        }
    }

    public void disconnect() throws ClientException {
        if (this.connectionThread == null || !this.connectionThread.isAlive()) {
            throw new ClientException("Not connected.");
        } else {
            try {
                this.connectionThread.getConnectionHandler().setWaitingDisconnection(true);
                this.connectionThread.getConnectionHandler().sendMessage(new ClientMessage(ClientCode.REQUEST_STOP));
                this.fireDisconnected(new EventObject(this));
            } catch (Exception ex) {
                throw new ClientException("Cannot disconnect from server.", ex);
            }
        }
    }

    public DisconnectionCause getDisconnectionCause() {
        return disconnectionCause;
    }

    private void setDisconnectionCause(DisconnectionCause disconnectionCause) {
        this.disconnectionCause = disconnectionCause;
    }

    public Exception getDisconnectionException() {
        return disconnectionException;
    }

    private void setDisconnectionException(Exception disconnectionException) {
        this.disconnectionException = disconnectionException;
    }

    public <T extends RemoteService> T getServerCallService(Class<T> classInterface) {
        return this.remoteDispatcher.getServerCallService(classInterface, connectionThread.getConnectionHandler());
    }

    public <T extends RemoteService> T getServerExecuteService(Class<T> classInterface) {        
        return this.remoteDispatcher.getServerExecuteService(classInterface, connectionThread.getConnectionHandler());
    }
    
    public void registerImplementation(Class<?> interfaceClass, Object implementation) {
        this.remoteDispatcher.registerImplementation(interfaceClass, implementation);        
    }

    // <editor-fold defaultstate="collapsed" desc="Events">
    public void addClientConnectionListener(ClientConnectionListener listener) {
        this.listenerList.add(ClientConnectionListener.class, listener);
    }

    public void removeClientConnectionListener(ClientConnectionListener listener) {
        this.listenerList.remove(ClientConnectionListener.class, listener);
    }

    void fireConnected(EventObject evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ClientConnectionListener.class) {
                ((ClientConnectionListener) listeners[i + 1]).connected(evt);
            }
        }
    }

    void fireDisconnected(EventObject evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ClientConnectionListener.class) {
                ((ClientConnectionListener) listeners[i + 1]).disconnected(evt);
            }
        }
    }

    public void addThreadListener(ThreadListener listener) {
        this.listenerList.add(ThreadListener.class, listener);
    }

    public void removeThreadListener(ThreadListener listener) {
        this.listenerList.remove(ThreadListener.class, listener);
    }

    void fireThreadCreated(ThreadCreatedEvent evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ThreadListener.class) {
                ((ThreadListener) listeners[i + 1]).threadCreated(evt);
            }
        }
    }

    public void addConnectionHandlerMessageTransportListener(ConnectionHandlerMessageTransportListener listener) {
        this.listenerList.add(ConnectionHandlerMessageTransportListener.class, listener);
    }

    public void removeConnectionHandlerMessageTransportListener(ConnectionHandlerMessageTransportListener listener) {
        this.listenerList.remove(ConnectionHandlerMessageTransportListener.class, listener);
    }

    void fireMessageTransported(ConnectionHandlerMessageTransportEvent evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ConnectionHandlerMessageTransportListener.class) {
                ((ConnectionHandlerMessageTransportListener) listeners[i + 1]).messageTransported(evt);
            }
        }
    }
    //</editor-fold>
}
